/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 */

package org.apache.solr.update;

import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.Term;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.Query;

import java.util.HashSet;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
import java.io.IOException;
import java.net.URL;

import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.update.UpdateHandler;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.SolrCore;

/**
 * <code>DirectUpdateHandler</code> implements an UpdateHandler where documents
 * are added directly to the main lucene index as opposed to adding to a
 * separate smaller index. For this reason, not all combinations to/from pending
 * and committed are supported.
 * 
 * @version $Id: DirectUpdateHandler.java 1297074 2012-03-05 14:40:17Z
 *          uschindler $
 * @since solr 0.9
 * 
 * @deprecated Use {@link DirectUpdateHandler2} instead. This is only kept
 *             around for back-compatibility (way back).
 */
@Deprecated
public class DirectUpdateHandler extends UpdateHandler {

	// the set of ids in the "pending set" (those docs that have been added, but
	// that are not yet visible.
	final HashSet<String> pset;
	IndexWriter writer;
	SolrIndexSearcher searcher;
	int numAdds = 0; // number of docs added to the pending set
	int numPending = 0; // number of docs currently in this pending set
	int numDeleted = 0; // number of docs deleted or

	public DirectUpdateHandler(SolrCore core) throws IOException {
		super(core);
		pset = new HashSet<String>(256);
	}

	protected void openWriter() throws IOException {
		if (writer == null) {
			writer = createMainIndexWriter("DirectUpdateHandler", false);
		}
	}

	protected void closeWriter() throws IOException {
		try {
			if (writer != null)
				writer.close();
		} finally {
			// TODO: if an exception causes the writelock to not be
			// released, we could delete it here.
			writer = null;
		}
	}

	protected void openSearcher() throws IOException {
		if (searcher == null) {
			searcher = core.newSearcher("DirectUpdateHandler");
		}
	}

	protected void closeSearcher() throws IOException {
		try {
			if (searcher != null)
				searcher.close();
		} finally {
			// TODO: if an exception causes the writelock to not be
			// released, we could delete it here.
			searcher = null;
		}
	}

	protected void doAdd(Document doc) throws IOException {
		closeSearcher();
		openWriter();
		writer.addDocument(doc);
	}

	protected boolean existsInIndex(String indexedId) throws IOException {
		if (idField == null)
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"Operation requires schema to have a unique key field");

		closeWriter();
		openSearcher();
		IndexReader ir = searcher.getReader();
		TermDocs tdocs = null;
		boolean exists = false;
		try {
			tdocs = ir.termDocs(idTerm(indexedId));
			if (tdocs.next())
				exists = true;
		} finally {
			try {
				if (tdocs != null)
					tdocs.close();
			} catch (Exception e) {
			}
		}
		return exists;
	}

	protected int deleteInIndex(String indexedId) throws IOException {
		if (idField == null)
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"Operation requires schema to have a unique key field");

		closeWriter();
		openSearcher();
		IndexReader ir = searcher.getReader();
		TermDocs tdocs = null;
		int num = 0;
		try {
			Term term = new Term(idField.getName(), indexedId);
			num = ir.deleteDocuments(term);
			if (SolrCore.log.isTraceEnabled()) {
				SolrCore.log.trace(core.getLogId() + "deleted " + num
						+ " docs matching id "
						+ idFieldType.indexedToReadable(indexedId));
			}
		} finally {
			try {
				if (tdocs != null)
					tdocs.close();
			} catch (Exception e) {
			}
		}
		return num;
	}

	protected void overwrite(String indexedId, Document doc) throws IOException {
		if (indexedId == null)
			indexedId = getIndexedId(doc);
		deleteInIndex(indexedId);
		doAdd(doc);
	}

	/**************
	 * Direct update handler - pseudo code *********** def add(doc, id,
	 * allowDups, overwritePending, overwriteCommitted): if not overwritePending
	 * and not overwriteCommitted: #special case... no need to check pending
	 * set, and we don't keep #any state around about this addition if
	 * allowDups: committed[id]=doc #100 return else: #if no dups allowed, we
	 * must check the *current* index (pending and committed) if not
	 * committed[id]: committed[id]=doc #000 return #001 (searchd
	 * addConditionally) if not allowDups and not overwritePending and
	 * pending[id]: return del committed[id] #delete from pending and committed
	 * 111 011 committed[id]=doc pending[id]=True
	 ****************************************************************/

	// could return the number of docs deleted, but is that always possible to
	// know???
	@Override
	public void delete(DeleteUpdateCommand cmd) throws IOException {
		if (!cmd.fromPending && !cmd.fromCommitted)
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"meaningless command: " + cmd);
		if (!cmd.fromPending || !cmd.fromCommitted)
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"operation not supported" + cmd);
		String indexedId = idFieldType.toInternal(cmd.id);
		synchronized (this) {
			deleteInIndex(indexedId);
			pset.remove(indexedId);
		}
	}

	// TODO - return number of docs deleted?
	// Depending on implementation, we may not be able to immediately determine
	// num...
	@Override
	public void deleteByQuery(DeleteUpdateCommand cmd) throws IOException {
		if (!cmd.fromPending && !cmd.fromCommitted)
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"meaningless command: " + cmd);
		if (!cmd.fromPending || !cmd.fromCommitted)
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"operation not supported: " + cmd);

		Query q = QueryParsing.parseQuery(cmd.query, schema);

		int totDeleted = 0;
		synchronized (this) {
			closeWriter();
			openSearcher();

			// if we want to count the number of docs that were deleted, then
			// we need a new instance of the DeleteHitCollector
			final DeleteHitCollector deleter = new DeleteHitCollector(searcher);
			searcher.search(q, null, deleter);
			totDeleted = deleter.deleted;
		}

		if (SolrCore.log.isDebugEnabled()) {
			SolrCore.log.debug(core.getLogId() + "docs deleted:" + totDeleted);
		}

	}

	/****************
	 * old hit collector... new one is in base class // final DeleteHitCollector
	 * deleter = new DeleteHitCollector(); class DeleteHitCollector extends
	 * HitCollector { public int deleted=0; public void collect(int doc, float
	 * score) { try { searcher.getReader().delete(doc); deleted++; } catch
	 * (IOException e) { try { closeSearcher(); } catch (Exception ee) {
	 * SolrException.log(SolrCore.log,ee); } SolrException.log(SolrCore.log,e);
	 * throw new SolrException(
	 * SolrException.StatusCode.SERVER_ERROR,"Error deleting doc# "+doc,e); } }
	 * }
	 ***************************/

	@Override
	public int mergeIndexes(MergeIndexesCommand cmd) throws IOException {
		throw new SolrException(
				SolrException.ErrorCode.BAD_REQUEST,
				"DirectUpdateHandler doesn't support mergeIndexes. Use DirectUpdateHandler2 instead.");
	}

	@Override
	public void commit(CommitUpdateCommand cmd) throws IOException {
		Future[] waitSearcher = null;
		if (cmd.waitSearcher) {
			waitSearcher = new Future[1];
		}

		synchronized (this) {
			pset.clear();
			closeSearcher(); // flush any deletes
			if (cmd.optimize || cmd.expungeDeletes) {
				openWriter(); // writer needs to be open to optimize
				if (cmd.optimize)
					writer.optimize(cmd.maxOptimizeSegments);
				if (cmd.expungeDeletes)
					writer.expungeDeletes(cmd.expungeDeletes);
			}
			closeWriter();

			callPostCommitCallbacks();
			if (cmd.optimize) {
				callPostOptimizeCallbacks();
			}

			core.getSearcher(true, false, waitSearcher);
		}

		if (waitSearcher != null && waitSearcher[0] != null) {
			try {
				waitSearcher[0].get();
			} catch (InterruptedException e) {
				SolrException.log(log, e);
			} catch (ExecutionException e) {
				SolrException.log(log, e);
			}
		}

		return;
	}

	/**
	 * @since Solr 1.4
	 */
	@Override
	public void rollback(RollbackUpdateCommand cmd) throws IOException {
		throw new SolrException(
				SolrException.ErrorCode.BAD_REQUEST,
				"DirectUpdateHandler doesn't support rollback. Use DirectUpdateHandler2 instead.");
	}

	// /////////////////////////////////////////////////////////////////
	// ///////////////// helper method for each add type ///////////////
	// /////////////////////////////////////////////////////////////////

	protected int addNoOverwriteNoDups(AddUpdateCommand cmd) throws IOException {
		if (cmd.indexedId == null) {
			cmd.indexedId = getIndexedId(cmd.doc);
		}
		synchronized (this) {
			if (existsInIndex(cmd.indexedId))
				return 0;
			doAdd(cmd.doc);
		}
		return 1;
	}

	protected int addConditionally(AddUpdateCommand cmd) throws IOException {
		if (cmd.indexedId == null) {
			cmd.indexedId = getIndexedId(cmd.doc);
		}
		synchronized (this) {
			if (pset.contains(cmd.indexedId))
				return 0;
			// since case 001 is currently the only case to use pset, only add
			// to it in that instance.
			pset.add(cmd.indexedId);
			overwrite(cmd.indexedId, cmd.doc);
			return 1;
		}
	}

	// overwrite both pending and committed
	protected synchronized int overwriteBoth(AddUpdateCommand cmd)
			throws IOException {
		overwrite(cmd.indexedId, cmd.doc);
		return 1;
	}

	// add without checking
	protected synchronized int allowDups(AddUpdateCommand cmd)
			throws IOException {
		doAdd(cmd.doc);
		return 1;
	}

	@Override
	public int addDoc(AddUpdateCommand cmd) throws IOException {

		// if there is no ID field, use allowDups
		if (idField == null) {
			cmd.allowDups = true;
			cmd.overwriteCommitted = false;
			cmd.overwritePending = false;
		}

		if (!cmd.allowDups && !cmd.overwritePending && !cmd.overwriteCommitted) {
			return addNoOverwriteNoDups(cmd);
		} else if (!cmd.allowDups && !cmd.overwritePending
				&& cmd.overwriteCommitted) {
			return addConditionally(cmd);
		} else if (!cmd.allowDups && cmd.overwritePending
				&& !cmd.overwriteCommitted) {
			// return overwriteBoth(cmd);
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"unsupported param combo:" + cmd);
		} else if (!cmd.allowDups && cmd.overwritePending
				&& cmd.overwriteCommitted) {
			return overwriteBoth(cmd);
		} else if (cmd.allowDups && !cmd.overwritePending
				&& !cmd.overwriteCommitted) {
			return allowDups(cmd);
		} else if (cmd.allowDups && !cmd.overwritePending
				&& cmd.overwriteCommitted) {
			// return overwriteBoth(cmd);
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"unsupported param combo:" + cmd);
		} else if (cmd.allowDups && cmd.overwritePending
				&& !cmd.overwriteCommitted) {
			// return overwriteBoth(cmd);
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"unsupported param combo:" + cmd);
		} else if (cmd.allowDups && cmd.overwritePending
				&& cmd.overwriteCommitted) {
			return overwriteBoth(cmd);
		}
		throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
				"unsupported param combo:" + cmd);
	}

	@Override
	public void close() throws IOException {
		synchronized (this) {
			closeSearcher();
			closeWriter();
		}
	}

	// ///////////////////////////////////////////////////////////////////
	// SolrInfoMBean stuff: Statistics and Module Info
	// ///////////////////////////////////////////////////////////////////

	public String getName() {
		return DirectUpdateHandler.class.getName();
	}

	public String getVersion() {
		return SolrCore.version;
	}

	public String getDescription() {
		return "Update handler that directly changes the on-disk main lucene index";
	}

	public Category getCategory() {
		return Category.CORE;
	}

	public String getSourceId() {
		return "$Id: DirectUpdateHandler.java 1297074 2012-03-05 14:40:17Z uschindler $";
	}

	public String getSource() {
		return "$URL: https://svn.apache.org/repos/asf/lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler.java $";
	}

	public URL[] getDocs() {
		return null;
	}

	public NamedList getStatistics() {
		NamedList lst = new SimpleOrderedMap();
		return lst;
	}

}
